package dslab.client;

import java.io.IOException;
import java.io.InputStream;
import java.io.PrintStream;
import java.io.Writer;
import java.rmi.NotBoundException;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.util.NoSuchElementException;
import java.util.Scanner;
import java.util.concurrent.Future;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import dslab.ComponentFactory;
import dslab.enums.Outcome;
import dslab.nameserver.INameserverRemote;
import dslab.util.Config;
import dslab.util.DMTPClient;
import dslab.util.Message;

public class MessageClient implements IMessageClient, Runnable {

    // configuration keys that have to be present
    private static final String[] requiredConfigKeys = new String[] {
            "transfer.host", "transfer.port", "transfer.email",
            "mailbox.host", "mailbox.port", "mailbox.user", "mailbox.password"
    };

    private final String componentId;
    private final Config config;
    private final InputStream in;
    private final PrintStream out;

    /**
     * creates a new client instance
     *
     * @param componentId the id of the component that corresponds to the Config resource
     * @param config the component config
     * @param in the input stream to read console input from
     * @param out the output stream to write console output to
     */
    public MessageClient(String componentId, Config config, InputStream in, PrintStream out)
            throws RemoteException, NotBoundException {
        // print configuration for the benefit of the user
        System.out.printf("@%s: configuration:%n", componentId);
        for (String key : config.listKeys())
            System.out.printf("@%s: { %s: %s }%n", componentId, key, config.getString(key));

        this.componentId = componentId;
        this.config = config;
        this.in = in;
        this.out = out;
    }

    @Override
    public void run() {
        try (Scanner scanner = new Scanner(in)) {
            String line;
            do {
                line = scanner.nextLine();
                processRequest(line);
            } while (!line.equals("shutdown"));
        } catch (NoSuchElementException ignored) {
        } finally {
            System.out.printf("sayonara%n");
        }
    }

    @Override
    public void inbox() {
        System.out.printf("%s: inbox called%n", componentId);
        // TODO
    }

    @Override
    public void delete(String id) {
        System.out.printf("%s: delete called with \"%s\"%n", componentId, id);
        // TODO
    }

    @Override
    public void verify(String id) {
        System.out.printf("%s: msg called with \"%s\"%n", componentId, id);
        // TODO
    }

    @Override
    public void msg(String to, String subject, String data) {
        System.out.printf("%s: msg called with \"%s\", \"%s\" and \"%s\"%n", componentId, to, subject, data);

        // query domain
        String domain = config.getString("transfer.email").split("@")[1];

        // set message contents
        Message message = new Message();
        message.sender = config.getString("transfer.email");
        message.recipients = to;
        message.subject = subject;
        message.data = data;

        // make DMTP client and send!
        Outcome outcome = new DMTPClient(
            String.format("%s-sender-%s", componentId, domain),
            message,
            config.getString("transfer.host"),
            config.getInt("transfer.port"),
            false
        ).call();

        // notify user of what has transpired
        if (outcome != Outcome.SUCCESS)
            System.err.printf("error: %s%n", outcome.getMessage());
        else
            System.out.printf("ok%n");
    }

    @Override
    public void shutdown() {
        System.out.printf("%s: shutdown called%n", componentId);
        // TODO
    }

    private void processRequest(String request) {
        String header;
        String body;

        if (request.contains(" ")) {
            // split multi-word instructions
            var headerAndBody = request.split(" ", 2);
            assert headerAndBody.length == 2;
            header = headerAndBody[0];
            body = headerAndBody[1];
        } else {
            // single word instructions have no body
            header = request;
            body = null;
        }

        // calls respective handlers and optionally does validation (to fail early)
        switch (header) {
            case "inbox":
                inbox();
                break;
            case "delete":
                if (body == null) {
                    System.err.printf("error: body required for command \"%s\"%n", header);
                    break;
                }
                delete(body);
                break;
            case "verify":
                if (body == null) {
                    System.err.printf("error: body required for command \"%s\"%n", header);
                    break;
                }
                verify(body);
                break;
            case "msg":
                if (body == null) {
                    System.err.printf("error: body required for command \"%s\"%n", header);
                    break;
                }
                Matcher matcher = Pattern.compile("^(\\S+) \"([^\"]+)\" \"([^\"]+)\"$").matcher(body);
                if (!matcher.find()) {
                    System.err.printf(
                        "cannot parse command body \"%s\" for command \"%s\"%n",
                        body,
                        header
                    );
                    break;
                }
                msg(matcher.group(1), matcher.group(2), matcher.group(3));
                break;
            case "shutdown":
                break;
            default:
                System.err.printf("command \"%s\" unknown%n", header);
                break;
        }
    }

    public static void main(String[] args) throws Exception {
        IMessageClient client = ComponentFactory.createMessageClient(args[0], System.in, System.out);
        client.run();
    }
}
